#include <QAbstractItemModel>
#include <QFileInfo>
#include <QHeaderView>
#include <QTableView>
#include <QVariant>
#include "csv-manager.h"
#include "debug-helper.h"
#include "hierarchical-header-view.h"
#include "model-helper.h"
#include "qxtcsvmodelex.h"

CsvManager::CsvManager (QObject *parent) :
    QObject(parent), m_tableView(0), m_state(InvalidState)
{
}

QxtCsvModelEx* CsvManager::importFromCsv (const QString& fileName, bool withHeader, const QChar& separator,
                                        QTextCodec* codec)
{
    Q_ASSERT(fileName.isEmpty() == false);
    if (fileName.isEmpty())
        return 0;

    QxtCsvModelEx* csvModel = new QxtCsvModelEx(this);
    csvModel->setSource(fileName, withHeader, separator, codec);
    return csvModel;
}

bool CsvManager::exportToCsv (const QTableView* tableView, const QString& fileName,
                              const SmGlobal::CellsRange cellsRange,
                              const SmGlobal::ColumnsVisibility columnsVisibility, const bool withHeader,
                              const bool forceNumbersFormat, const QChar& separator, QTextCodec* codec)
{
    Q_ASSERT(tableView);
    Q_ASSERT(!fileName.isEmpty());

    m_tableView = tableView;

    QxtCsvModelEx csvModel;
    setModelHeaderData(csvModel, columnsVisibility, withHeader, m_tableView->model());
    setModelData(csvModel, findIndexes(cellsRange, columnsVisibility));

    if (csvModel.rowCount() == 0 && csvModel.columnCount() == 0)
    {
        setState(NothingToExport);
        return false;
    }

    if (!csvModel.toCSV(fileName, withHeader, forceNumbersFormat, separator, codec))
    {
        setState(CannotExportToFile);
        return false;
    }

    setState(ExportSuccessful);
    return true;
}

void CsvManager::setModelData (QxtCsvModelEx& csvModel, const QModelIndexList& indexes)
{
    if (indexes.isEmpty())
        return;

    int rowCount = findRowCount(indexes, csvModel.columnCount());
    Q_ASSERT(rowCount > 0);
    csvModel.insertRows(0, rowCount);

    int indexesIterator = 0;
    for (int row = 0; row < csvModel.rowCount(); row++)
    {
        for (int column = 0; column < csvModel.columnCount(); column++)
        {
            csvModel.setData(csvModel.index(row, column), indexes.at(indexesIterator).data(Qt::DisplayRole));
            indexesIterator++;
        }
    }
}

void CsvManager::setModelHeaderData (QxtCsvModelEx& csvModel, const SmGlobal::ColumnsVisibility columnsVisibility,
                                     const bool withHeader, QAbstractItemModel* model)
{
    if (withHeader)
    {
        QAbstractItemModel* headerModel = ModelHelper::findHierarchicalHeaderModel(model, Qt::Horizontal);
        if (headerModel)
        {
            QList<QStringList> headerData = findHeaderData(headerModel, columnsVisibility);
            if (headerData.isEmpty())
                return;

            csvModel.insertColumns(0, headerData.at(0).size());
            csvModel.setHeaderData(headerData);
        }
        else
        {
            QStringList headerData = findHeaderData(columnsVisibility);
            if (headerData.isEmpty())
                return;

            csvModel.insertColumns(0, headerData.size());
            csvModel.setHeaderData(headerData);
        }
    }
}

void CsvManager::setState (CsvManager::ExportState state)
{
    m_state = state;
}

CsvManager::ExportState CsvManager::state() const
{
    return m_state;
}

QModelIndexList CsvManager::findIndexes (const SmGlobal::CellsRange cellsRange,
                                         const SmGlobal::ColumnsVisibility columnsVisibility)
{
    QModelIndexList indexes;
    switch (cellsRange)
    {
    case SmGlobal::AllCells:
        switch (columnsVisibility)
        {
        case SmGlobal::AllColumns:
            indexes = ModelHelper::allIndexes(m_tableView);
            break;

        case SmGlobal::VisibleColumns:
            indexes = ModelHelper::visibleIndexes(m_tableView);
            break;

        default:
            fatalError(columnsVisibility);
            return QModelIndexList();
        }
        break;

    case SmGlobal::SelectedCells:
        switch (columnsVisibility)
        {
        case SmGlobal::AllColumns:
            indexes = ModelHelper::selectedIndexes(m_tableView);
            break;

        case SmGlobal::VisibleColumns:
            indexes = ModelHelper::selectedVisibleIndexes(m_tableView);
            break;

        default:
            fatalError(columnsVisibility);
            return QModelIndexList();
        }
        break;

    default:
        fatalError(cellsRange);
        return QModelIndexList();
    }

#ifndef QT_NO_DEBUG
    // we assume the indexes are already sorted
    for (int i = 1; i < indexes.length(); i++)
        Q_ASSERT(indexes.at(i-1) < indexes.at(i));
#endif

    return indexes;
}

int CsvManager::findRowCount (const QModelIndexList& indexes, int columnCount)
{
    // we assume that every row has the same number of columns
    // but there may be "holes" between rows (not selected rows between selected ones)
    if (indexes.isEmpty())
        return 0;

    if (columnCount == -1)
        columnCount = findColumnCount(indexes);

#ifndef QT_NO_DEBUG
    // check if column count is valid
    int lastCheckedRow = indexes.at(0).row();
    int column = 0;
    for (int i = 0; i < indexes.size(); i++)
    {
        if (lastCheckedRow < indexes.at(i).row())
        {
            Q_ASSERT(column == columnCount);
            lastCheckedRow = indexes.at(i).row();
            column = 1;
        }
        else
        {
            column++;
            if (i == indexes.size()-1)
                Q_ASSERT(column == columnCount);
        }
    }
#endif

    int rowCount = 0;
    int lastRow = -1;

    for (int i = 0; i < indexes.size(); i++)
    {
        if (lastRow < indexes.at(i).row())
        {
            lastRow = indexes.at(i).row();
            rowCount++;
            i += columnCount;
        }
    }

    return rowCount;
}

int CsvManager::findColumnCount (const QModelIndexList& indexes)
{
    // we assume that every row has the same number of columns
    // but there may be "holes" between columns (i.e. because of hidden columns)
    if (indexes.isEmpty())
        return 0;

#ifndef QT_NO_DEBUG
    int lastCheckedRow = indexes.at(0).row();
    int columnsInRow = 0;
    for (int i = 0; i < indexes.size(); i++)
    {
        if (lastCheckedRow < indexes.at(i).row())
        {
            columnsInRow = i;
            lastCheckedRow = indexes.at(i).row();
            break;
        }
    }

    int column = 0;
    for (int i = columnsInRow; i < indexes.size(); i++)
    {
        if (lastCheckedRow < indexes.at(i).row())
        {
            Q_ASSERT(column == columnsInRow);
            lastCheckedRow = indexes.at(i).row();
            column = 1;
        }
        else
        {
            column++;
            if (i == indexes.size()-1 && lastCheckedRow != indexes.at(0).row())
                Q_ASSERT(column == columnsInRow);
        }
    }
#endif

    int lastRow = indexes.at(0).row();
    for (int i = 0; i < indexes.size(); i++)
        if (lastRow < indexes.at(i).row())
            return i;

    // if there is only one row
    return indexes.size();
}

QStringList CsvManager::findHeaderData (const SmGlobal::ColumnsVisibility columnsVisibility)
{
    Q_ASSERT(columnsVisibility == SmGlobal::AllColumns || columnsVisibility == SmGlobal::VisibleColumns);
    QStringList headerData;

    if (columnsVisibility == SmGlobal::AllColumns)
    {
        for (int i = 0; i < m_tableView->horizontalHeader()->count(); i++)
            headerData.append(m_tableView->model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
    }
    else
    {
        for (int i = 0; i < m_tableView->horizontalHeader()->count(); i++)
        {
            if (!m_tableView->horizontalHeader()->isSectionHidden(i))
                headerData.append(m_tableView->model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
        }
    }

    return headerData;
}

QList<QStringList> CsvManager::findHeaderData (QAbstractItemModel* headerModel,
                                               const SmGlobal::ColumnsVisibility columnsVisibility)
{
    Q_ASSERT(columnsVisibility == SmGlobal::AllColumns || columnsVisibility == SmGlobal::VisibleColumns);
    Q_ASSERT(headerModel != 0);
    if (headerModel == 0)
        return QList<QStringList>();

    int bottomSectionCount = 0;
    if (columnsVisibility == SmGlobal::AllColumns)
    {
        bottomSectionCount = ModelHelper::bottomSectionCount(headerModel);
    }
    else
    {
        ModelHelper::setVisibleLeafCount(headerModel, m_tableView);
        bottomSectionCount = ModelHelper::bottomVisibleSectionCount(headerModel);
    }

    if (bottomSectionCount == 0)
        return QList<QStringList>();

    QList<QStringList> headerData;
    headerData.append(createEmptyRow(bottomSectionCount));
    int offset = 0;
    int realSection = 0;
    for (int section = 0; section < headerModel->columnCount(); section++)
    {
        QModelIndex item = headerModel->index(0, section);
        if (headerModel->data(item, SmGlobal::SectionTypeRole).toInt() == SmGlobal::SingleSection)
        {
            if (columnsVisibility == SmGlobal::AllColumns)
            {
                headerData.last().replace(section + offset, headerModel->data(headerModel->index(0, section),
                           Qt::DisplayRole).toString());
            }
            else
            {
                if (!m_tableView->isColumnHidden(realSection))
                {
                    headerData.last().replace(section + offset, headerModel->data(headerModel->index(0, section),
                               Qt::DisplayRole).toString());
                }
                else
                {
                    offset--;
                }

                realSection++;
            }
        }
        else
        {
            QList<QStringList> multipleSectionData = findMultipleSectionData(item, headerModel, columnsVisibility);

            while (headerData.size() < multipleSectionData.size())
                headerData.prepend(createEmptyRow(headerData.last().size()));

            for (int row = 0; row < multipleSectionData.size(); row++)
                for (int column = 0; column < multipleSectionData.at(row).size(); column++)
                    headerData[headerData.size()-multipleSectionData.size()+row].replace(section+offset+column,
                               multipleSectionData.at(row).at(column));

            if (columnsVisibility == SmGlobal::AllColumns)
                offset += item.data(SmGlobal::LeafCountRole).toInt()-1;
            else
                offset += item.data(SmGlobal::VisibleLeafCountRole).toInt()-1;

            realSection += item.data(SmGlobal::LeafCountRole).toInt();
        }
    }

    return headerData;
}

QList<QStringList> CsvManager::findMultipleSectionData (const QModelIndex& item, QAbstractItemModel* headerModel,
                                                        const SmGlobal::ColumnsVisibility columnsVisibility)
{
    Q_ASSERT(columnsVisibility == SmGlobal::AllColumns || columnsVisibility == SmGlobal::VisibleColumns);

    QList<QStringList> multipleSectionData;
    if (columnsVisibility == SmGlobal::AllColumns)
        findMultipleSectionData(item, 0, 0, multipleSectionData, headerModel);
    else
        findMultipleSectionVisibleData(item, 0, 0, multipleSectionData, headerModel);

    return multipleSectionData;
}


void CsvManager::findMultipleSectionData (const QModelIndex& item, const int position, const int level,
                                          QList<QStringList>& headerDataList, QAbstractItemModel* headerModel)
{
    while (headerDataList.size()-1 < level)
        headerDataList.append(createEmptyRow(position-1));

    QModelIndex leftSibling(item.sibling(0, position-1));
    if (leftSibling.isValid())
        resizeRow(leftSibling.data(SmGlobal::LeafCountRole).toInt(), level, headerDataList);

    headerDataList[level].append(headerModel->data(item, Qt::DisplayRole).toString());

    for (int column = 0; column < headerModel->columnCount(item); column++)
        findMultipleSectionData(item.child(0, column), position+column, level+1, headerDataList, headerModel);
}

void CsvManager::findMultipleSectionVisibleData (const QModelIndex& item, const int position, const int level,
                                                 QList<QStringList>& headerDataList, QAbstractItemModel* headerModel)
{
    if (item.data(SmGlobal::VisibleLeafCountRole).toInt() == 0)
        return;

    while (headerDataList.size()-1 < level)
        headerDataList.append(createEmptyRow(position-1));

    QModelIndex leftSibling(item.sibling(0, position-1));
    if (leftSibling.isValid())
        resizeRow(leftSibling.data(SmGlobal::VisibleLeafCountRole).toInt(), level, headerDataList);

    headerDataList[level].append(headerModel->data(item, Qt::DisplayRole).toString());

    for (int column = 0; column < headerModel->columnCount(item); column++)
        findMultipleSectionVisibleData(item.child(0, column), position+column, level+1, headerDataList, headerModel);
}

QStringList CsvManager::createEmptyRow (const int size)
{
    QStringList row;
    for (int i = 0; i < size; i++)
        row.append("");

    return row;
}

void CsvManager::resizeRows (const int newSize, QList<QStringList>& dataList)
{
    for (int row = 0; row < dataList.size(); row++)
        resizeRow(newSize, row, dataList);
}

void CsvManager::resizeRow (const int newSize, const int row, QList<QStringList>& dataList)
{
    QStringList& data = dataList[row];
    for (int column = data.size()-1; data.size() < newSize; column++)
        data.append("");
}

QString CsvManager::path() const
{
    return QFileInfo(m_filePath).canonicalPath();
}

QString CsvManager::filePath() const
{
    return m_filePath;
}
